package com.cuhk.gulimall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.cuhk.common.to.es.SkuEsModel;
import com.cuhk.common.utils.R;
import com.cuhk.gulimall.search.config.ElasticSearchConfig;
import com.cuhk.gulimall.search.constant.EsConstants;
import com.cuhk.gulimall.search.feign.ProductFeignService;
import com.cuhk.gulimall.search.service.MallSearchService;
import com.cuhk.gulimall.search.vo.AttrFeignRespVo;
import com.cuhk.gulimall.search.vo.SearchParam;
import com.cuhk.gulimall.search.vo.SearchResult;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Service
public class MallSearchServiceImpl implements MallSearchService {

    @Autowired
    private RestHighLevelClient esRestClient;

    @Autowired
    private ProductFeignService productFeignService;

    public static final String URL_PREFIX = "http://localhost:14000/list.html";

    public static final String ENCODE = "UTF-8";

    private SearchRequest buildSearchRequest(SearchParam param) {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        /**
         * 1、模糊匹配
         */
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        // 1.1、按照关键字模糊匹配
        if(!StringUtils.isEmpty(param.getKeyword())){
            boolQueryBuilder.must(QueryBuilders.matchQuery(EsConstants.PRODUCT_SKU_TITLE, param.getKeyword()));
        }
        // 1.2、按照三级分类id
        if(param.getCatelogLevel3Id() != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery(EsConstants.PRODUCT_CATELOG_ID, param.getCatelogLevel3Id()));
        }
        // 1.3、按照品牌id
        if(param.getBrandIds() != null && !param.getBrandIds().isEmpty()) {
            // 多值匹配的term查询, 使用termsQuery
            boolQueryBuilder.filter(QueryBuilders.termsQuery(EsConstants.PRODUCT_BRAND_ID, param.getBrandIds()));
        }
        // 1.4、按照是否有库存查询
        if(param.getHasStock() != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery(EsConstants.PRODUCT_HAS_STOCK, param.getHasStock() == 1));
        }
        // 1.5、按照价格区间
        if(!StringUtils.isEmpty(param.getSkuPrice())){
            String[] boundary = param.getSkuPrice().split("_");
            if(boundary.length == 2){
                // 100_500
                if(!StringUtils.isEmpty(boundary[0]) && !StringUtils.isEmpty(boundary[1])){
                    RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(EsConstants.PRODUCT_SKU_PRICE);
                    boolQueryBuilder.filter(rangeQuery.gte(boundary[0]).lte(boundary[1]));
                }
                // _500
                else {
                    RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(EsConstants.PRODUCT_SKU_PRICE);
                    boolQueryBuilder.filter(rangeQuery.lte(boundary[1]));
                }
            }
            // 500_
            else {
                RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(EsConstants.PRODUCT_SKU_PRICE);
                boolQueryBuilder.filter(rangeQuery.gte(boundary[0]));
            }
        }
        // 1.6、按照属性列表
        if(param.getAttrs() != null && !param.getAttrs().isEmpty()) {
            for(String attr : param.getAttrs()) {
                BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
                String[] level1 = attr.split("_");
                // 属性id
                String attrId = level1[0];
                // 属性可能取值
                String[] level2 = level1[1].split(":");
                nestedBoolQuery.must(QueryBuilders.termQuery(EsConstants.PRODUCT_ATTR_ID, attrId));
                nestedBoolQuery.must(QueryBuilders.termsQuery(EsConstants.PRODUCT_ATTR_VALUE, level2));
                NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery(EsConstants.PRODUCT_ATTR_NESTED_PATH, nestedBoolQuery, ScoreMode.None);
                boolQueryBuilder.filter(nestedQueryBuilder);
            }
        }
        searchSourceBuilder.query(boolQueryBuilder);

        /**
         * 2、排序
         */
        if(!StringUtils.isEmpty(param.getSort())){
            String[] sortInfo = param.getSort().split("_");
            searchSourceBuilder.sort(sortInfo[0], sortInfo[1].equalsIgnoreCase("asc") ? SortOrder.ASC : SortOrder.DESC);
        }

        /**
         * 3、分页
         */
        // 默认是第一页
        int pageNo = param.getPageNum() == null ? 1 : param.getPageNum();
        searchSourceBuilder.from((pageNo - 1) * EsConstants.PRODUCT_PAGE_SIZE);
        searchSourceBuilder.size(EsConstants.PRODUCT_PAGE_SIZE);

        /**
         * 4、高亮
         */
        if(!StringUtils.isEmpty(param.getKeyword())){
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            // 设置需要高亮的属性
            highlightBuilder.field(EsConstants.PRODUCT_SKU_TITLE);
            highlightBuilder.preTags("<b style='color:yellow'>");
            highlightBuilder.postTags("</b>");
            searchSourceBuilder.highlighter(highlightBuilder);
        }

        /**
         * 5、聚合
         */

        TermsAggregationBuilder brandAgg = AggregationBuilders.terms(EsConstants.PRODUCT_BRAND_AGG);
        brandAgg.field(EsConstants.PRODUCT_BRAND_ID).size(50);


        //5.1、 品牌的子聚合-品牌名聚合
        brandAgg.subAggregation(AggregationBuilders.terms(EsConstants.PRODUCT_BRAND_NAME_AGG)
                .field(EsConstants.PRODUCT_BRAND_NAME).size(1));
        //5.1、 品牌的子聚合-品牌图片聚合
        brandAgg.subAggregation(AggregationBuilders.terms(EsConstants.PRODUCT_BRAND_IMG_AGG)
                .field(EsConstants.PRODUCT_BRAND_IMG).size(1));

        searchSourceBuilder.aggregation(brandAgg);

        //5.2、 按照分类信息进行聚合
        TermsAggregationBuilder catelogAgg = AggregationBuilders.terms(EsConstants.PRODUCT_CATELOG_AGG);
        catelogAgg.field(EsConstants.PRODUCT_CATELOG_ID).size(20);

        catelogAgg.subAggregation(AggregationBuilders.terms(EsConstants.PRODUCT_CATELOG_NAME_AGG).field(EsConstants.PRODUCT_CATELOG_NAME).size(1));

        searchSourceBuilder.aggregation(catelogAgg);

        //5.3、 按照属性信息进行聚合
        NestedAggregationBuilder attrAgg = AggregationBuilders.nested(EsConstants.PRODUCT_ATTR_AGG, EsConstants.PRODUCT_ATTR_NESTED_PATH);
        // 按照属性ID进行聚合
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms(EsConstants.PRODUCT_ATTR_ID_AGG).field(EsConstants.PRODUCT_ATTR_ID);
        attrAgg.subAggregation(attrIdAgg);
        // 在每个属性ID下，按照属性名进行聚合
        attrIdAgg.subAggregation(AggregationBuilders.terms(EsConstants.PRODUCT_ATTR_NAME_AGG).field(EsConstants.PRODUCT_ATTR_NAME).size(1));
        // 在每个属性ID下，按照属性值进行聚合
        attrIdAgg.subAggregation(AggregationBuilders.terms(EsConstants.PRODUCT_ATTR_VALUE_AGG).field(EsConstants.PRODUCT_ATTR_VALUE).size(50));
        searchSourceBuilder.aggregation(attrAgg);


        String s = searchSourceBuilder.toString();
        System.out.println("DSL语句: " + s);
        return new SearchRequest(new String[]{EsConstants.PRODUCT_INDEX}, searchSourceBuilder);
    }

    private SearchResult buildSearchResult(SearchResponse searchResponse, SearchParam param, String url) {
        SearchResult searchResult = new SearchResult();
        // 查询命中结果
        SearchHits hits = searchResponse.getHits();
        SearchHit[] searchHits = hits.getHits();
        if(searchHits != null) {
            for(SearchHit hit : searchHits) {
                // 对象的JSON字符串
                String sourceAsString = hit.getSourceAsString();
                SkuEsModel item = JSON.parseObject(sourceAsString, SkuEsModel.class);
                // 对根据keyword做的高亮信息特殊提取
                if(!StringUtils.isEmpty(param.getKeyword())){
                    Map<String, HighlightField> map = hit.getHighlightFields();
                    HighlightField highlightField = map.get(EsConstants.PRODUCT_SKU_TITLE);
                    item.setSkuTitle(highlightField.getFragments()[0].string());
                }
                searchResult.addProducts(item);
            }
        }

        Aggregations aggregations = searchResponse.getAggregations();
        if(aggregations != null) {

            // 聚合得到的类别信息
            ParsedLongTerms catelogTerms = aggregations.get(EsConstants.PRODUCT_CATELOG_AGG);
            for(Terms.Bucket bucket : catelogTerms.getBuckets()) {
                SearchResult.CatelogVo vo = new SearchResult.CatelogVo();
                // 分类ID
                String catelogId = bucket.getKeyAsString();
                vo.setCatelogId(Long.parseLong(catelogId));
                // 分类名字
                ParsedStringTerms catelogNameAgg = bucket.getAggregations().get(EsConstants.PRODUCT_CATELOG_NAME_AGG);
                String keyName = catelogNameAgg.getBuckets().get(0).getKeyAsString();
                vo.setCatelogName(keyName);
                searchResult.addCatelogs(vo);
            }

            // 聚合得到的品牌信息
            ParsedLongTerms brandTerms = aggregations.get(EsConstants.PRODUCT_BRAND_AGG);
            for(Terms.Bucket bucket : brandTerms.getBuckets()) {
                SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
                String brandId = bucket.getKeyAsString();
                brandVo.setBrandId(Long.parseLong(brandId));
                // 聚合出来的brandName
                ParsedStringTerms brandNameAgg = bucket.getAggregations().get(EsConstants.PRODUCT_BRAND_NAME_AGG);
                String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
                brandVo.setBrandName(brandName);
                // 聚合出来的brandImg
                ParsedStringTerms brandImgAgg = bucket.getAggregations().get(EsConstants.PRODUCT_BRAND_IMG_AGG);
                String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();
                brandVo.setBrandImg(brandImg);

                searchResult.addBrand(brandVo);
            }

            // 聚合得到属性信息
            ParsedNested attrNested = aggregations.get(EsConstants.PRODUCT_ATTR_AGG);
            Aggregations attrAggs = attrNested.getAggregations();
            ParsedLongTerms attrIdAgg = attrAggs.get(EsConstants.PRODUCT_ATTR_ID_AGG);
            for(Terms.Bucket bucket : attrIdAgg.getBuckets()) {
                SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
                Long attrId = bucket.getKeyAsNumber().longValue();
                attrVo.setAttrId(attrId);
                // 属性名称
                ParsedStringTerms attrNameAgg = bucket.getAggregations().get(EsConstants.PRODUCT_ATTR_NAME_AGG);
                String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
                attrVo.setAttrName(attrName);
                // 属性值
                ParsedStringTerms attrValueAgg = bucket.getAggregations().get(EsConstants.PRODUCT_ATTR_VALUE_AGG);
                List<String> attrValue = new ArrayList<>();
                for(Terms.Bucket attrValueBucket : attrValueAgg.getBuckets()) {
                    String val = attrValueBucket.getKeyAsString();
                    attrValue.add(val);
                }
                attrVo.setAttrValue(attrValue);
                searchResult.addAttr(attrVo);
            }
        }


        // 总记录数
        long totalHits = hits.getTotalHits().value;
        searchResult.setTotal(totalHits);
        // 总页码
        int tmp = (int) totalHits / EsConstants.PRODUCT_PAGE_SIZE;
        int totalPages = totalHits % EsConstants.PRODUCT_PAGE_SIZE == 0 ? tmp : tmp + 1;
        searchResult.setTotalPages(totalPages);
        // 当前页码
        searchResult.setPageNum(param.getPageNum());

        // 填充可遍历页码列表
        searchResult.fillPageViews();

        // 构建面包屑导航用的数据
        System.out.println("这是请求的url: " + url);
        List<String> attrs =  param.getAttrs();
        if(attrs != null && !attrs.isEmpty()) {
            List<SearchResult.NavVo> navVoList = new ArrayList<>();
            String afterCancelURL = "";
            for(String attr : attrs) {
                // 属性attrs: attr=${attr_id}_${val1:val2:...:valn}
                String[] info = attr.split("_");
                String attrId = info[0];
                // String[] attrValues = info[1].split(":");
                R res = productFeignService.attrInfo(Long.parseLong(attrId));
                if(res.get("code").equals(0)) {
                    AttrFeignRespVo vo = res.getData(new TypeReference<AttrFeignRespVo>() {}, "attr");
                    SearchResult.NavVo navVo = new SearchResult.NavVo();
                    navVo.setNavName(vo.getAttrName());
                    navVo.setNavValue(info[1]);

                    // 设置取消这个面包屑后，页面应该跳转的url地址
                    String encoded = null;
                    try {
                        encoded = URLEncoder.encode(attr, ENCODE);
                        // 浏览器解析数据和后端编码数据的差异化处理
                        // 后端使用utf-8编码，会将空格转化为+,但是浏览器传过来的是%20
                        encoded = encoded.replace("+", "%20");
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                    String target = "&attrs=" + encoded;
                    String replacedURL = url.replace(target, "");
                    // 要替换掉的attr参数在是请求中的第一个参数
                    if(replacedURL.equals(url)) {
                        target = "attrs=" + encoded;
                        if(url.contains("&")) {
                            target += "&";
                            replacedURL = url.replace(target, "");
                            afterCancelURL = URL_PREFIX + "?" + replacedURL;
                        }
                        else {
                            afterCancelURL = URL_PREFIX;
                        }
                    }
                    else {
                        afterCancelURL = URL_PREFIX + "?" + replacedURL;
                    }
                    // String targetUrl = URL_PREFIX + replacedURL;
                    navVo.setLinkAfterCancel(afterCancelURL);
                    navVoList.add(navVo);
                }
            }
            searchResult.setNavs(navVoList);
        }


        return searchResult;
    }

    @Override
    public SearchResult search(SearchParam param, String url) {
        // 构建检索的DSL语句
        SearchRequest searchRequest = buildSearchRequest(param);
        // 执行检索请求
        SearchResponse searchResponse = null;
        try {
            searchResponse = esRestClient.search(searchRequest, ElasticSearchConfig.COMMON_OPTIONS);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return buildSearchResult(searchResponse, param, url);
    }
}
